// Copyright 2017 Baidu Inc. All Rights Reserved.
// Author: Chen Xihao (chenxihao@baidu.com)
//
// Description: Adapter for hls

#include "baidu_hls_adapter.h"
#include <aes.h>
#include <stdlib.h>
#include <string.h>
#include <Thread.h>
#include "baidu_http_client.h"
#include "baidu_iot_mutex.h"
#include "baidu_media_play.h"
#include "baidu_media_type.h"
#include "duer_log.h"
#include "heap_monitor.h"
#include "baidu_measure_stack.h"

using rtos::Thread;

namespace duer {

struct Buffer {
    uint8_t* data;
    size_t size;
    size_t limit;
    size_t position;
};

static thread_entry s_method = NULL;
static void* s_this = NULL;
static check_need_to_stop_hls s_need_stop = NULL;
static int s_stop_download_flag = 0;

static int check_stop_download() {
    if (s_need_stop) {
        return s_need_stop() | s_stop_download_flag;
    }
    return s_stop_download_flag;
}

static int download_callback(
        void* p_user_ctx,
        e_data_pos pos,
        const char* buf,
        size_t len,
        const char* type) {
    if (p_user_ctx == NULL || buf == NULL || len <= 0) {
        return -1;
    }

    Buffer* buffer = (Buffer*)p_user_ctx;

    // httpclient cann't download m3u8 file from position,
    // so add below code
    if (buffer->position >= len) {
        buffer->position -= len;
        return 0;
    } else if (buffer->position > 0) {
        len -= buffer->position;
        buf = buf + buffer->position;
        buffer->position = 0;
    } else {
        // do nothing
    }

    if (buffer->size + len >= buffer->limit) {
        DUER_LOGI("download data size:%d is bigger than limit.", buffer->size + len);
        s_stop_download_flag = 1;
    }

    buffer->data = (uint8_t*)REALLOC(buffer->data, buffer->size + len + 1, MEDIA);
    if (buffer->data == NULL) {
        DUER_LOGE("Not enough memory!");
        return -1;
    }

    memcpy(buffer->data + buffer->size, buf, len);
    buffer->size += len;
    buffer->data[buffer->size] = 0;

    return 0;
}

static int get_short_data_from_url(
        const char* url,
        check_need_to_stop_hls need_stop,
        size_t limit,
        size_t position,
        uint8_t** data,
        size_t* len) {
    Buffer buffer;
    memset(&buffer, 0, sizeof(buffer));
    buffer.limit = limit;
    buffer.position = position;

    s_need_stop = need_stop;
    s_stop_download_flag = 0;

    HttpClient http_client;
    http_client.register_data_handler(download_callback, &buffer);
    http_client.register_notify_call_back(check_stop_download);
    int ret = http_client.get(url, 0);

    if (ret < 0) {
        FREE(buffer.data);
    } else {
        *data = buffer.data;
        *len = buffer.size;
    }

    s_need_stop = NULL;

    return ret;
}

static int download_segment_callback(
        void* p_user_ctx,
        e_data_pos pos,
        const char* buf,
        size_t len,
        const char* type) {
    if (p_user_ctx == NULL) {
        return -1;
    }

    hls_download_context* context = (hls_download_context*)p_user_ctx;
    if (!context->b_first_segment && pos == DATA_FIRST || pos == DATA_LAST) {
        if (buf == NULL || len < 1) {
            return 0;
        }
        pos = DATA_MID;
    }

    return context->handle_segment_data(context->stream, context->segment,
            (const uint8_t*)buf, len, pos);
}

static int download_segment(
        const char* url,
        check_need_to_stop_hls need_stop,
        hls_download_context* context) {
    HttpClient http_client;
    http_client.register_data_handler(download_segment_callback, context);
    http_client.register_notify_call_back(need_stop);

    return http_client.get(url, 0);
}

static int output_ts_data(const uint8_t* data, size_t len, int pos) {
    MediaPlayerMessage msg;
    switch (pos) {
    case DATA_FIRST: {
        msg.cmd = PLAYER_CMD_START;
        break;
    }
    case DATA_MID: {
        if (data == NULL || len < 1) {
            DUER_LOGE("output_ts_data DATA_MID buf is NULL!");
            return -1;
        }
        msg.cmd = PLAYER_CMD_CONTINUE;
        break;
    }
    case DATA_LAST: {
        msg.cmd = PLAYER_CMD_STOP;
        break;
    }
    default:
        DUER_LOGE("Invalid pos!");
        return -1;
    }
    msg.size = len;
    msg.type = TYPE_TS;

    g_media_play_buffer->write(&msg, sizeof(msg));
    g_media_play_buffer->write(data, len);

    return 0;
}

static uint32_t current_time() {
    return us_ticker_read();
}

static int thread_wait(uint32_t millisec) {
    return Thread::wait(millisec) == 0 ? 0 : -1;
}

static void* thread_create() {
    Thread* t = NEW(MEDIA) Thread(osPriorityAboveNormal, DEFAULT_STACK_SIZE * 2);
#ifdef BAIDU_STACK_MONITOR
    register_thread(t, "HLS");
#endif
    return t;
}

static void thread_destroy(void* thread) {
    if (thread == NULL) {
        return;
    }

    Thread* t = (Thread*)thread;
#ifdef BAIDU_STACK_MONITOR
    unregister_thread(t);
#endif
    if (t->get_state() != Thread::Deleted) {
        t->terminate();
    }
    delete t;
}

static void thread_run() {
    if (s_method != NULL) {
        s_method(s_this);
    }
}

static int thread_start(void* thread, thread_entry method, void* p_this) {
    if (thread == NULL) {
        return -1;
    }

    Thread* t = (Thread*)thread;
    s_method = method;
    s_this = p_this;
    return t->start(thread_run) == 0 ? 0 : -1;
}

static void* mutex_create(void) {
    return iot_mutex_create();
}

static int mutex_lock(void* mutex) {
    return iot_mutex_lock(mutex, osWaitForever);
}

static int mutex_unlock(void* mutex) {
    return iot_mutex_unlock(mutex);
}

static void mutex_destroy(void* mutex) {
    iot_mutex_destroy(mutex);
}

static void* aes_context_create() {
    mbedtls_aes_context* ctx = (mbedtls_aes_context*)MALLOC(sizeof(mbedtls_aes_context), MEDIA);
    if (ctx != NULL) {
        mbedtls_aes_init(ctx);
    }

    return ctx;
}

static int aes_setkey_dec(void* ctx, const uint8_t* key) {
    if (ctx == NULL || key == NULL) {
        return -1;
    }

    return mbedtls_aes_setkey_dec((mbedtls_aes_context*)ctx, key, 128);
}

static int aes_decrypt_cbc(void* ctx, size_t length, uint8_t iv[AES_BLOCK_SIZE],
        const uint8_t* input, uint8_t* output) {
    return mbedtls_aes_crypt_cbc((mbedtls_aes_context*)ctx, MBEDTLS_AES_DECRYPT,
            length, iv, input, output);
}

static void aes_context_destroy(void* ctx) {
    if (ctx != NULL) {
        mbedtls_aes_free((mbedtls_aes_context*)ctx);
        FREE(ctx);
    }
}

} // namespace duer

hls_adapter* create_hls_adapter(void) {
    hls_adapter* adapter = (hls_adapter*)MALLOC(sizeof(hls_adapter), MEDIA);
    if (adapter == NULL) {
        DUER_LOGE("No enough memory");
        return NULL;
    }

    adapter->get_short_data_from_url = duer::get_short_data_from_url;
    adapter->current_time = duer::current_time;
    adapter->wait = duer::thread_wait;

    adapter->thread_create = duer::thread_create;
    adapter->thread_destroy = duer::thread_destroy;
    adapter->thread_start = duer::thread_start;

    adapter->mutex_create = duer::mutex_create;
    adapter->mutex_lock = duer::mutex_lock;
    adapter->mutex_unlock = duer::mutex_unlock;
    adapter->mutex_destroy = duer::mutex_destroy;

    adapter->aes_context_create = duer::aes_context_create;
    adapter->aes_setkey_dec = duer::aes_setkey_dec;
    adapter->aes_decrypt_cbc = duer::aes_decrypt_cbc;
    adapter->aes_context_destroy = duer::aes_context_destroy;

    adapter->download_segment = duer::download_segment;
    adapter->output_ts_data = duer::output_ts_data;

    return adapter;
}
